1use std::num::NonZero;
2
3use crate::common::Rational32;
4
5#[derive(Debug, Clone, Copy)]
7pub struct ObjectHandle {
8 pub(crate) internal: aviutl2_sys::plugin2::OBJECT_HANDLE,
9}
10impl From<aviutl2_sys::plugin2::OBJECT_HANDLE> for ObjectHandle {
11 fn from(value: aviutl2_sys::plugin2::OBJECT_HANDLE) -> Self {
12 Self { internal: value }
13 }
14}
15impl From<ObjectHandle> for aviutl2_sys::plugin2::OBJECT_HANDLE {
16 fn from(value: ObjectHandle) -> Self {
17 value.internal
18 }
19}
20
21unsafe impl Send for ObjectHandle {}
23unsafe impl Sync for ObjectHandle {}
24
25#[derive(Debug, Clone, Copy)]
31pub struct EditInfo {
32 pub width: usize,
34 pub height: usize,
36 pub fps: Rational32,
38 pub sample_rate: usize,
40 pub frame: usize,
42 pub layer: usize,
44 pub frame_max: usize,
46 pub layer_max: usize,
48 pub display_frame_start: usize,
50 pub display_layer_start: usize,
52 pub display_frame_num: usize,
58 pub display_layer_num: usize,
64 pub select_range_start: Option<usize>,
67 pub select_range_end: Option<usize>,
70 pub grid_bpm_tempo: f32,
72 pub grid_bpm_beat: usize,
74 pub grid_bpm_offset: f32,
76 pub scene_id: i32,
78}
79
80impl EditInfo {
81 pub unsafe fn from_raw(ptr: *const aviutl2_sys::plugin2::EDIT_INFO) -> Self {
85 let raw = unsafe { &*ptr };
86 Self {
87 width: raw.width as usize,
88 height: raw.height as usize,
89 fps: Rational32::new(raw.rate, raw.scale),
90 sample_rate: raw.sample_rate as usize,
91 frame: raw.frame as usize,
92 layer: raw.layer as usize,
93 frame_max: raw.frame_max as usize,
94 layer_max: raw.layer_max as usize,
95 display_frame_start: raw.display_frame_start as usize,
96 display_layer_start: raw.display_layer_start as usize,
97 display_frame_num: raw.display_frame_num as usize,
98 display_layer_num: raw.display_layer_num as usize,
99
100 #[expect(clippy::unnecessary_lazy_evaluations)]
103 select_range_start: (raw.select_range_start >= 0)
104 .then(|| raw.select_range_start as usize),
105 #[expect(clippy::unnecessary_lazy_evaluations)]
106 select_range_end: (raw.select_range_end >= 0).then(|| raw.select_range_end as usize),
107
108 grid_bpm_tempo: raw.grid_bpm_tempo,
109 grid_bpm_beat: raw.grid_bpm_beat as usize,
110 grid_bpm_offset: raw.grid_bpm_offset,
111 scene_id: raw.scene_id,
112 }
113 }
114}
115
116#[derive(Debug, Clone, Copy)]
118pub struct ObjectLayerFrame {
119 pub layer: usize,
120 pub start: usize,
121 pub end: usize,
122}
123
124#[derive(Debug, Clone, Copy)]
126pub struct LayerFrameData {
127 pub layer: usize,
128 pub frame: usize,
129}
130
131#[derive(Debug, Clone, Copy)]
132pub struct MediaInfo {
133 pub video_track_num: Option<NonZero<usize>>,
135 pub audio_track_num: Option<NonZero<usize>>,
137 pub total_time: f64,
139 pub width: usize,
141 pub height: usize,
143}
144
145#[derive(Debug, Clone, Copy)]
147pub enum MediaFileSupportMode {
148 ExtensionOnly,
150 Strict,
152}
153
154#[derive(thiserror::Error, Debug)]
156pub enum EditSectionError {
157 #[error("api call failed")]
158 ApiCallFailed,
159 #[error("object does not exist")]
160 ObjectDoesNotExist,
161 #[error("input utf-8 string contains null byte")]
162 InputCstrContainsNull(#[from] std::ffi::NulError),
163 #[error("input utf-16 string contains null byte")]
164 InputCwstrContainsNull(#[from] crate::common::NullByteError),
165 #[error("value is out of range")]
166 ValueOutOfRange(#[from] std::num::TryFromIntError),
167 #[error("api returned non-utf8 data")]
168 NonUtf8Data(#[from] std::str::Utf8Error),
169
170 #[cfg(feature = "aviutl2-alias")]
171 #[error("alias parse error: {0}")]
172 ParseFailed(#[from] aviutl2_alias::TableParseError),
173}
174
175pub type EditSectionResult<T> = Result<T, EditSectionError>;
176
177#[derive(Debug)]
179pub struct EditSection {
180 pub info: EditInfo,
182
183 pub(crate) internal: *mut aviutl2_sys::plugin2::EDIT_SECTION,
184}
185
186impl EditSection {
187 pub unsafe fn from_raw(ptr: *mut aviutl2_sys::plugin2::EDIT_SECTION) -> Self {
193 Self {
194 internal: ptr,
195 info: unsafe { EditInfo::from_raw((*ptr).info) },
196 }
197 }
198
199 pub fn create_object_from_alias(
215 &self,
216 alias: &str,
217 layer: usize,
218 frame: usize,
219 length: usize,
220 ) -> EditSectionResult<ObjectHandle> {
221 let c_alias = std::ffi::CString::new(alias)?;
222 let object_handle = unsafe {
223 ((*self.internal).create_object_from_alias)(
224 c_alias.as_ptr(),
225 layer.try_into()?,
226 frame.try_into()?,
227 length.try_into()?,
228 )
229 };
230 if object_handle.is_null() {
231 return Err(EditSectionError::ApiCallFailed);
232 }
233 Ok(ObjectHandle {
234 internal: object_handle,
235 })
236 }
237
238 pub fn find_object_after(
245 &self,
246 layer: usize,
247 frame: usize,
248 ) -> EditSectionResult<Option<ObjectHandle>> {
249 let object_handle =
250 unsafe { ((*self.internal).find_object)(layer.try_into()?, frame.try_into()?) };
251 if object_handle.is_null() {
252 Ok(None)
253 } else {
254 Ok(Some(ObjectHandle {
255 internal: object_handle,
256 }))
257 }
258 }
259
260 pub fn count_object_effect(
271 &self,
272 object: &ObjectHandle,
273 effect: &str,
274 ) -> EditSectionResult<usize> {
275 self.ensure_object_exists(object)?;
276 let c_effect = crate::common::CWString::new(effect)?;
277 let count =
278 unsafe { ((*self.internal).count_object_effect)(object.internal, c_effect.as_ptr()) };
279 Ok(count.try_into()?)
280 }
281
282 pub fn get_object_layer_frame(
284 &self,
285 object: &ObjectHandle,
286 ) -> EditSectionResult<ObjectLayerFrame> {
287 self.ensure_object_exists(object)?;
288 let object = unsafe { ((*self.internal).get_object_layer_frame)(object.internal) };
289 Ok(ObjectLayerFrame {
290 layer: object.layer.try_into()?,
291 start: object.start.try_into()?,
292 end: object.end.try_into()?,
293 })
294 }
295
296 pub fn get_object_alias(&self, object: &ObjectHandle) -> EditSectionResult<String> {
298 self.ensure_object_exists(object)?;
299 let alias_ptr = unsafe { ((*self.internal).get_object_alias)(object.internal) };
300 if alias_ptr.is_null() {
301 return Err(EditSectionError::ApiCallFailed);
302 }
303 let c_str = unsafe { std::ffi::CStr::from_ptr(alias_ptr) };
304 let alias = c_str.to_str()?.to_owned();
305 Ok(alias)
306 }
307
308 pub fn get_object_name(&self, object: &ObjectHandle) -> EditSectionResult<Option<String>> {
314 self.ensure_object_exists(object)?;
315 let name_ptr = unsafe { ((*self.internal).get_object_name)(object.internal) };
316 if name_ptr.is_null() {
317 return Ok(None);
318 }
319 Ok(Some(unsafe { crate::common::load_wide_string(name_ptr) }))
320 }
321
322 pub fn set_object_name(
328 &self,
329 object: &ObjectHandle,
330 name: Option<&str>,
331 ) -> EditSectionResult<()> {
332 self.ensure_object_exists(object)?;
333 match name {
334 None => {
335 unsafe { ((*self.internal).set_object_name)(object.internal, std::ptr::null()) };
336 Ok(())
337 }
338 Some(name) => {
339 let c_name = crate::common::CWString::new(name)?;
340 unsafe {
341 ((*self.internal).set_object_name)(object.internal, c_name.as_ptr());
342 }
343 Ok(())
344 }
345 }
346 }
347
348 pub fn get_object_effect_item(
357 &self,
358 object: &ObjectHandle,
359 effect_name: &str,
360 effect_index: usize,
361 item: &str,
362 ) -> EditSectionResult<String> {
363 self.ensure_object_exists(object)?;
364 let c_effect_name = crate::common::CWString::new(&effect_key(effect_name, effect_index))?;
365 let c_item = crate::common::CWString::new(item)?;
366 let value_ptr = unsafe {
367 ((*self.internal).get_object_item_value)(
368 object.internal,
369 c_effect_name.as_ptr(),
370 c_item.as_ptr(),
371 )
372 };
373 if value_ptr.is_null() {
374 return Err(EditSectionError::ApiCallFailed);
375 }
376 let c_str = unsafe { std::ffi::CStr::from_ptr(value_ptr) };
377 let value = c_str.to_str()?.to_owned();
378 Ok(value)
379 }
380
381 pub fn set_object_effect_item(
391 &self,
392 object: &ObjectHandle,
393 effect_name: &str,
394 effect_index: usize,
395 item: &str,
396 value: &str,
397 ) -> EditSectionResult<()> {
398 self.ensure_object_exists(object)?;
399 let c_effect_name = crate::common::CWString::new(&effect_key(effect_name, effect_index))?;
400 let c_item = crate::common::CWString::new(item)?;
401 let c_value = std::ffi::CString::new(value)?;
402 let success = unsafe {
403 ((*self.internal).set_object_item_value)(
404 object.internal,
405 c_effect_name.as_ptr(),
406 c_item.as_ptr(),
407 c_value.as_ptr(),
408 )
409 };
410 if !success {
411 return Err(EditSectionError::ApiCallFailed);
412 }
413 Ok(())
414 }
415
416 pub fn move_object(
418 &self,
419 object: &ObjectHandle,
420 new_layer: usize,
421 new_start_frame: usize,
422 ) -> EditSectionResult<()> {
423 self.ensure_object_exists(object)?;
424 let success = unsafe {
425 ((*self.internal).move_object)(
426 object.internal,
427 new_layer.try_into()?,
428 new_start_frame.try_into()?,
429 )
430 };
431 if !success {
432 return Err(EditSectionError::ApiCallFailed);
433 }
434 Ok(())
435 }
436
437 pub fn delete_object(&self, object: &ObjectHandle) -> EditSectionResult<()> {
439 self.ensure_object_exists(object)?;
440 unsafe { ((*self.internal).delete_object)(object.internal) };
441 Ok(())
442 }
443
444 pub fn get_focused_object(&self) -> EditSectionResult<Option<ObjectHandle>> {
446 let object_handle = unsafe { ((*self.internal).get_focus_object)() };
447 if object_handle.is_null() {
448 Ok(None)
449 } else {
450 Ok(Some(ObjectHandle {
451 internal: object_handle,
452 }))
453 }
454 }
455
456 pub fn get_selected_objects(&self) -> EditSectionResult<Vec<ObjectHandle>> {
458 let mut handles = Vec::new();
459 let num_objects = unsafe { ((*self.internal).get_selected_object_num)() };
460 for i in 0..num_objects {
461 let object_handle = unsafe { ((*self.internal).get_selected_object)(i) };
462 if object_handle.is_null() {
463 return Err(EditSectionError::ApiCallFailed);
464 }
465 handles.push(ObjectHandle {
466 internal: object_handle,
467 });
468 }
469 Ok(handles)
470 }
471
472 pub fn focus_object(&self, object: &ObjectHandle) -> EditSectionResult<()> {
478 self.ensure_object_exists(object)?;
479 unsafe { ((*self.internal).set_focus_object)(object.internal) };
480 Ok(())
481 }
482
483 pub fn get_project_file<'handle>(
485 &'handle self,
486 edit_handle: &crate::generic::EditHandle,
487 ) -> crate::generic::ProjectFile<'handle> {
488 let pf_ptr = unsafe { ((*self.internal).get_project_file)(edit_handle.internal) };
489 unsafe { crate::generic::ProjectFile::from_raw(pf_ptr) }
490 }
491
492 pub fn get_mouse_layer_frame(&self) -> EditSectionResult<Option<LayerFrameData>> {
499 let mut layer = 0;
500 let mut frame = 0;
501 let on_layer_edit =
502 unsafe { ((*self.internal).get_mouse_layer_frame)(&mut layer, &mut frame) };
503 if on_layer_edit {
504 Ok(Some(LayerFrameData {
505 layer: layer.try_into()?,
506 frame: frame.try_into()?,
507 }))
508 } else {
509 Ok(None)
510 }
511 }
512
513 pub fn pos_to_layer_frame(&self, x: i32, y: i32) -> EditSectionResult<Option<LayerFrameData>> {
515 let mut layer = 0;
516 let mut frame = 0;
517 let on_layer_edit =
518 unsafe { ((*self.internal).pos_to_layer_frame)(x, y, &mut layer, &mut frame) };
519 if on_layer_edit {
520 Ok(Some(LayerFrameData {
521 layer: layer.try_into()?,
522 frame: frame.try_into()?,
523 }))
524 } else {
525 Ok(None)
526 }
527 }
528
529 pub fn is_support_media_file<P: AsRef<str>>(
531 &self,
532 file_path: P,
533 mode: MediaFileSupportMode,
534 ) -> EditSectionResult<bool> {
535 let c_file_path = crate::common::CWString::new(file_path.as_ref())?;
536 let is_supported = unsafe {
537 match mode {
538 MediaFileSupportMode::ExtensionOnly => {
539 ((*self.internal).is_support_media_file)(c_file_path.as_ptr(), false)
540 }
541 MediaFileSupportMode::Strict => {
542 ((*self.internal).is_support_media_file)(c_file_path.as_ptr(), true)
543 }
544 }
545 };
546 Ok(is_supported)
547 }
548
549 pub fn get_media_info<P: AsRef<str>>(&self, file_path: P) -> EditSectionResult<MediaInfo> {
555 let c_file_path = crate::common::CWString::new(file_path.as_ref())?;
556 let mut media_info = std::mem::MaybeUninit::<aviutl2_sys::plugin2::MEDIA_INFO>::uninit();
557 let success = unsafe {
558 ((*self.internal).get_media_info)(
559 c_file_path.as_ptr(),
560 media_info.as_mut_ptr(),
561 std::mem::size_of::<aviutl2_sys::plugin2::MEDIA_INFO>() as i32,
562 )
563 };
564 if !success {
565 return Err(EditSectionError::ApiCallFailed);
566 }
567 let media_info = unsafe { media_info.assume_init() };
568 Ok(MediaInfo {
569 video_track_num: NonZero::new(media_info.video_track_num.try_into()?),
570 audio_track_num: NonZero::new(media_info.audio_track_num.try_into()?),
571 total_time: media_info.total_time,
572 width: media_info.width.try_into()?,
573 height: media_info.height.try_into()?,
574 })
575 }
576
577 pub fn create_object_from_media_file<P: AsRef<str>>(
586 &self,
587 file_path: P,
588 layer: usize,
589 frame: usize,
590 length: Option<usize>,
591 ) -> EditSectionResult<ObjectHandle> {
592 let c_file_path = crate::common::CWString::new(file_path.as_ref())?;
593 let object_handle = unsafe {
594 ((*self.internal).create_object_from_media_file)(
595 c_file_path.as_ptr(),
596 layer.try_into()?,
597 frame.try_into()?,
598 length.unwrap_or(0).try_into()?,
599 )
600 };
601 if object_handle.is_null() {
602 return Err(EditSectionError::ApiCallFailed);
603 }
604 Ok(ObjectHandle {
605 internal: object_handle,
606 })
607 }
608
609 pub fn create_object(
618 &self,
619 effect: &str,
620 layer: usize,
621 frame: usize,
622 length: Option<usize>,
623 ) -> EditSectionResult<ObjectHandle> {
624 let c_effect = crate::common::CWString::new(effect)?;
625 let object_handle = unsafe {
626 ((*self.internal).create_object)(
627 c_effect.as_ptr(),
628 layer.try_into()?,
629 frame.try_into()?,
630 length.unwrap_or(0).try_into()?,
631 )
632 };
633 if object_handle.is_null() {
634 return Err(EditSectionError::ApiCallFailed);
635 }
636 Ok(ObjectHandle {
637 internal: object_handle,
638 })
639 }
640
641 pub fn set_cursor_layer_frame(&self, layer: usize, frame: usize) -> EditSectionResult<()> {
647 unsafe {
648 ((*self.internal).set_cursor_layer_frame)(layer.try_into()?, frame.try_into()?);
649 }
650 Ok(())
651 }
652
653 pub fn set_display_layer_frame(&self, layer: usize, frame: usize) -> EditSectionResult<()> {
659 unsafe {
660 ((*self.internal).set_display_layer_frame)(layer.try_into()?, frame.try_into()?);
661 }
662 Ok(())
663 }
664
665 pub fn set_select_range(&self, start: usize, end: usize) -> EditSectionResult<()> {
671 unsafe {
672 ((*self.internal).set_select_range)(start.try_into()?, end.try_into()?);
673 }
674 Ok(())
675 }
676
677 pub fn clear_select_range(&self) -> EditSectionResult<()> {
679 unsafe {
680 ((*self.internal).set_select_range)(-1, -1);
681 }
682 Ok(())
683 }
684
685 pub fn set_grid_bpm(&self, tempo: f32, beat: usize, offset: f32) -> EditSectionResult<()> {
687 unsafe {
688 ((*self.internal).set_grid_bpm)(tempo, beat.try_into()?, offset);
689 }
690 Ok(())
691 }
692
693 pub fn object_exists(&self, object: &ObjectHandle) -> bool {
699 let object = unsafe { ((*self.internal).get_object_layer_frame)(object.internal) };
700 object.layer != -1
701 }
702
703 fn ensure_object_exists(&self, object: &ObjectHandle) -> EditSectionResult<()> {
704 if !self.object_exists(object) {
705 return Err(EditSectionError::ObjectDoesNotExist);
706 }
707 Ok(())
708 }
709
710 pub fn layers(&self) -> EditSectionLayersIterator<'_> {
712 EditSectionLayersIterator::new(self)
713 }
714
715 pub fn layer<'a>(&'a self, layer: usize) -> EditSectionLayerCaller<'a> {
717 EditSectionLayerCaller::new(self, layer)
718 }
719 pub fn object<'a>(&'a self, object: &'a ObjectHandle) -> EditSectionObjectCaller<'a> {
721 EditSectionObjectCaller::new(self, object)
722 }
723}
724
725pub struct EditSectionObjectCaller<'a> {
729 edit_section: &'a EditSection,
730 pub handle: &'a ObjectHandle,
731}
732impl<'a> EditSectionObjectCaller<'a> {
733 pub fn new(edit_section: &'a EditSection, object: &'a ObjectHandle) -> Self {
734 Self {
735 edit_section,
736 handle: object,
737 }
738 }
739
740 pub fn get_layer_frame(&self) -> EditSectionResult<ObjectLayerFrame> {
742 self.edit_section.get_object_layer_frame(self.handle)
743 }
744
745 pub fn get_alias(&self) -> EditSectionResult<String> {
747 self.edit_section.get_object_alias(self.handle)
748 }
749
750 #[cfg(feature = "aviutl2-alias")]
752 pub fn get_alias_parsed(&self) -> EditSectionResult<aviutl2_alias::Table> {
753 self.edit_section
754 .get_object_alias(self.handle)?
755 .parse()
756 .map_err(Into::into)
757 }
758
759 pub fn count_effect(&self, effect: &str) -> EditSectionResult<usize> {
769 self.edit_section.count_object_effect(self.handle, effect)
770 }
771
772 pub fn get_effect_item(
780 &self,
781 effect_name: &str,
782 effect_index: usize,
783 item: &str,
784 ) -> EditSectionResult<String> {
785 self.edit_section
786 .get_object_effect_item(self.handle, effect_name, effect_index, item)
787 }
788
789 pub fn set_effect_item(
798 &self,
799 effect_name: &str,
800 effect_index: usize,
801 item: &str,
802 value: &str,
803 ) -> EditSectionResult<()> {
804 self.edit_section.set_object_effect_item(
805 self.handle,
806 effect_name,
807 effect_index,
808 item,
809 value,
810 )
811 }
812
813 pub fn move_object(&self, new_layer: usize, new_start_frame: usize) -> EditSectionResult<()> {
820 self.edit_section
821 .move_object(self.handle, new_layer, new_start_frame)
822 }
823
824 pub fn delete_object(&self) -> EditSectionResult<()> {
826 self.edit_section.delete_object(self.handle)
827 }
828
829 pub fn focus_object(&self) -> EditSectionResult<()> {
835 self.edit_section.focus_object(self.handle)
836 }
837
838 pub fn exists(&self) -> bool {
840 self.edit_section.object_exists(self.handle)
841 }
842}
843
844pub struct EditSectionLayerCaller<'a> {
848 edit_section: &'a EditSection,
849 pub index: usize,
850}
851impl<'a> EditSectionLayerCaller<'a> {
852 pub fn new(edit_section: &'a EditSection, layer: usize) -> Self {
853 Self {
854 edit_section,
855 index: layer,
856 }
857 }
858
859 pub fn find_object_after(&self, frame: usize) -> EditSectionResult<Option<ObjectHandle>> {
865 self.edit_section.find_object_after(self.index, frame)
866 }
867
868 pub fn create_object_from_media_file<P: AsRef<str>>(
874 &self,
875 file_path: P,
876 frame: usize,
877 length: Option<usize>,
878 ) -> EditSectionResult<ObjectHandle> {
879 self.edit_section
880 .create_object_from_media_file(file_path, self.index, frame, length)
881 }
882
883 pub fn create_object(
889 &self,
890 effect: &str,
891 frame: usize,
892 length: Option<usize>,
893 ) -> EditSectionResult<ObjectHandle> {
894 self.edit_section
895 .create_object(effect, self.index, frame, length)
896 }
897
898 pub fn objects(&self) -> EditSectionLayerObjectsIterator<'a> {
901 EditSectionLayerObjectsIterator::new(self.edit_section, self.index)
902 }
903}
904
905#[derive(Debug, Clone)]
907pub struct EditSectionLayersIterator<'a> {
908 edit_section: &'a EditSection,
909 current: usize,
910 total: usize,
911}
912
913impl<'a> EditSectionLayersIterator<'a> {
914 fn new(edit_section: &'a EditSection) -> Self {
915 Self {
916 edit_section,
917 current: 0,
918 total: edit_section.info.layer_max,
919 }
920 }
921}
922
923impl<'a> Iterator for EditSectionLayersIterator<'a> {
924 type Item = EditSectionLayerCaller<'a>;
925
926 fn next(&mut self) -> Option<Self::Item> {
927 if self.current > self.total {
928 return None;
929 }
930 let layer = self.current;
931 self.current += 1;
932 Some(EditSectionLayerCaller::new(self.edit_section, layer))
933 }
934}
935
936#[derive(Debug, Clone)]
939pub struct EditSectionLayerObjectsIterator<'a> {
940 edit_section: &'a EditSection,
941 layer: usize,
942 next_frame: usize,
943}
944
945impl<'a> EditSectionLayerObjectsIterator<'a> {
946 fn new(edit_section: &'a EditSection, layer: usize) -> Self {
947 Self {
948 edit_section,
949 layer,
950 next_frame: 0,
951 }
952 }
953}
954
955impl<'a> Iterator for EditSectionLayerObjectsIterator<'a> {
956 type Item = (ObjectLayerFrame, ObjectHandle);
957
958 fn next(&mut self) -> Option<Self::Item> {
959 let Ok(Some(handle)) = self
961 .edit_section
962 .find_object_after(self.layer, self.next_frame)
963 else {
964 return None;
965 };
966
967 let lf = match self.edit_section.get_object_layer_frame(&handle) {
968 Ok(lf) => lf,
969 Err(_) => return None,
970 };
971
972 self.next_frame = lf.end.saturating_add(1);
974
975 Some((lf, handle))
976 }
977}
978
979fn effect_key(effect_name: &str, effect_index: usize) -> String {
980 format!("{effect_name}:{effect_index}")
981}